using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using OrbitOne.Elmah.Data;

namespace OrbitOne.Elmah.Logic
{
    /// <summary>
    /// Represents the search view factory
    /// </summary>
    internal class SearchViewFactory
    {
        #region Properties
        /// <summary>
        /// Gets the ErrorEntities the factory uses.
        /// </summary>
        public IQueryable<ELMAH_Error> ErrorEntities { get; private set; }
        #endregion

        #region Constructor
        /// <summary>
        /// Creates a search view view factory.
        /// </summary>
        /// <param name="errorEntities">An <code>IQueryable:OrbitOneElmahErrorEntity</code> that the search view factory will use to return its results.</param>
        public SearchViewFactory(IQueryable<ELMAH_Error> errorEntities)
        {
            this.ErrorEntities = errorEntities;
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Creates a search view based on the ErrorEntities.
        /// </summary>
        /// <param name="view">the view that should be returned.</param>
        /// <returns>An <code>IEnumerable:SearchRecord</code></returns>
        public SearchRecordCollection CreateSearchView(ErrorSearchFilter.ResultViewGroup view, int startRowIndex, int maximumRows)
        {
            SearchRecordCollection result;
            switch (view)
            {
                case ErrorSearchFilter.ResultViewGroup.URL:
                    result = GetUrlSearchRecords(startRowIndex, maximumRows);
                    break;
                case ErrorSearchFilter.ResultViewGroup.ListView:
                    result = GetListViewSearchRecords(startRowIndex, maximumRows);
                    break;
                case ErrorSearchFilter.ResultViewGroup.ExceptionType:
                    result = GetExceptionTypeSearchRecords(startRowIndex, maximumRows);
                    break;
                case ErrorSearchFilter.ResultViewGroup.ExceptionMessage:
                    result = GetExceptionMessageSearchRecords(startRowIndex, maximumRows);
                    break;
                default:
                    throw new NotImplementedException();
            }
            return result;
        }
        #endregion

        #region Private Methods
        private SearchRecordCollection GetUrlSearchRecords(int startRowIndex, int maximumRows)
        {
            var entities = ErrorEntities.ToList();
            var groupedEntities = entities.GroupBy(error => new
                                                                {
                                                                    error.ApplicationName,
                                                                    Url = error.AllXml
                                                                .DescendantsAndSelf("error")
                                                                .Descendants("serverVariables")
                                                                .Descendants("item")
                                                                .Where(el => el.Attribute("name") != null && el.Attribute("name").Value == "URL")
                                                                .Descendants("value")
                                                                .Attributes("string")
                                                                .FirstOrDefault()
                                                                .Value
                                                                });
            var count = groupedEntities.Count();
            var records = groupedEntities.OrderByDescending(order => DateTime.Now - order.Max(error => error.HappeningTime))
                .Select(errorGroup => new SearchRecord(
                                          errorGroup.Key.ApplicationName + errorGroup.Key.Url,
                                          errorGroup.OrderByDescending(error => error.HappeningTime)
                                              .Select(
                                              error =>
                                              new OrbitOneElmahErrorFacade(error.ErrorId,
                                                                           error.HappeningTime,
                                                                           error.HostName,
                                                                           error.ApplicationName,
                                                                           error.ErrorMessage,
                                                                           error.ErrorType,
                                                                           error.ErrorLevel,
                                                                           error.UserName,
                                                                           error.AppliedRules)),
                                          errorGroup.Select(error => error.ErrorId).Count(),
                                          errorGroup.Select(error => error.HappeningTime).Max()))
                .Skip(startRowIndex)
                .Take(maximumRows)
                .ToList();

            return new SearchRecordCollection(records, count);
        }

        private SearchRecordCollection GetListViewSearchRecords(int startRowIndex, int maximumRows)
        {
            var errors = ErrorEntities.Select(error => new OrbitOneElmahErrorFacade(error.ErrorId,
                                                                                    error.HappeningTime,
                                                                                    error.HostName,
                                                                                    error.ApplicationName,
                                                                                    error.ErrorMessage,
                                                                                    error.ErrorType,
                                                                                    error.ErrorLevel,
                                                                                    error.UserName,
                                                                                    error.AppliedRules))
                .ToList()
                .OrderByDescending(error => error.HappeningTime)
                .Skip(startRowIndex)
                .Take(maximumRows);
            var count = ErrorEntities.Select(error => error.ErrorId).Count();
            var date = new DateTime();
            if (count != 0) date = ErrorEntities.Select(error => error.HappeningTime).Max();
            var searchRecord = new SearchRecord(null, errors, count, date);
            return new SearchRecordCollection(new List<SearchRecord>{searchRecord}, 1);
        }

        private SearchRecordCollection GetExceptionMessageSearchRecords(int startRowIndex, int maximumRows)
        {
            var entities = ErrorEntities.ToList();
            var groupedEntities = entities.GroupBy(error => error.ErrorMessage);
            var count = groupedEntities.Count();
            var records = groupedEntities.Select(errorGroup => new SearchRecord(errorGroup.Key,
                                                                                errorGroup.Select(error => new OrbitOneElmahErrorFacade(error.ErrorId,
                                                                                                                                        error.HappeningTime,
                                                                                                                                        error.HostName,
                                                                                                                                        error.ApplicationName,
                                                                                                                                        error.ErrorMessage,
                                                                                                                                        error.ErrorType,
                                                                                                                                        error.ErrorLevel,
                                                                                                                                        error.UserName,
                                                                                                                                        error.AppliedRules))
                                                                                    .OrderByDescending(error => error.HappeningTime).Take(20).ToList(),
                                                                                errorGroup.Select(error => error.ErrorId).Count(),
                                                                                errorGroup.Select(error => error.HappeningTime).Max()))
                .OrderByDescending(record => record.Count)
                .Skip(startRowIndex)
                .Take(maximumRows)
                .ToList();
            return new SearchRecordCollection(records, count);
        }

        private SearchRecordCollection GetExceptionTypeSearchRecords(int startRowIndex, int maximumRows)
        {
            var entities = ErrorEntities.ToList();
            var groupedEntities = entities.GroupBy(error => error.ErrorType);
            var count = groupedEntities.Count();
            var records = groupedEntities.Select(errorGroup => new SearchRecord(errorGroup.Key,
                                                                                errorGroup.OrderByDescending(error => error.HappeningTime)
                                                                                    .Select(error => new OrbitOneElmahErrorFacade(error.ErrorId,
                                                                                                                                  error.HappeningTime,
                                                                                                                                  error.HostName,
                                                                                                                                  error.ApplicationName,
                                                                                                                                  error.ErrorMessage,
                                                                                                                                  error.ErrorType,
                                                                                                                                  error.ErrorLevel,
                                                                                                                                  error.UserName,
                                                                                                                                  error.AppliedRules)).Take(20).ToList(),
                                                                                errorGroup.Select(error => error.ErrorId).Count(),
                                                                                errorGroup.Select(error => error.HappeningTime).Max()))
                .OrderByDescending(record => record.Count)
                .Skip(startRowIndex)
                .Take(maximumRows)
                .ToList();
            return new SearchRecordCollection(records, count);
        }
        #endregion
    }
}